_init: function(source) {
this._source = source;
+ // Holds the WindowClone objects for our windows, used in the button release
+ // callback to find the window actor we released over
+ this._cachedWindowClones = [];
+
+ // Whether or not we successfully picked a window
+ this.didActivateWindow = false;
this.actor = new Shell.GenericContainer({ reactive: true });
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this._windowContainer.connect('activate', Lang.bind(this, this._onWindowActivate));
this.actor.add_actor(this._windowContainer);
+ // Stay popped up on release over application icon
+ this._windowContainer.set_persistent_source(this._source.actor);
+
+ // Intercept events while the menu has the pointer grab to do window-related effects
+ this._windowContainer.connect('enter-event', Lang.bind(this, this._onMenuEnter));
+ this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave));
+ this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuRelease));
+
this._arrow = new Shell.DrawingArea();
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
Shell.draw_box_pointer(texture, WELL_MENU_BORDER_COLOR, WELL_MENU_BACKGROUND_COLOR);
_redisplay: function() {
this._windowContainer.remove_all();
+ this.didActivateWindow = false;
+
let windows = this._source.windows;
this._windowContainer.show();
_appendWindows: function (windows, iconsDiffer) {
for (let i = 0; i < windows.length; i++) {
- let window = windows[i];
+ let metaWindow = windows[i];
+
+ this._cachedWindowClones.push(Main.overview.lookupCloneForWindow(metaWindow));
+
/* Use padding here rather than spacing in the box above so that
* we have a larger reactive area.
*/
padding_bottom: 4,
spacing: 4,
reactive: true });
- box._window = window;
+ box._window = metaWindow;
let vCenter;
if (iconsDiffer) {
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
- let icon = Shell.TextureCache.get_default().bind_pixbuf_property(window, "mini-icon");
+ let icon = Shell.TextureCache.get_default().bind_pixbuf_property(metaWindow, "mini-icon");
vCenter.append(icon, Big.BoxPackFlags.NONE);
box.append(vCenter, Big.BoxPackFlags.NONE);
}
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
- let label = new Clutter.Text({ text: window.title,
+ let label = new Clutter.Text({ text: metaWindow.title,
font_name: WELL_MENU_FONT,
ellipsize: Pango.EllipsizeMode.END,
color: WELL_MENU_COLOR });
this.actor.show();
},
- _onWindowUnselected: function (actor, child) {
- child.background_color = TRANSPARENT_COLOR;
+ _findWindowCloneForActor: function (actor) {
+ for (let i = 0; i < this._cachedWindowClones.length; i++) {
+ let clone = this._cachedWindowClones[i];
+ if (clone.actor == actor) {
+ return clone;
+ }
+ }
+ return null;
+ },
- this.emit('highlight-window', null);
+ // This function is called while the menu has a pointer grab; what we want
+ // to do is see if the mouse was released over a window clone actor
+ _onMenuRelease: function (actor, event) {
+ let clone = this._findWindowCloneForActor(event.get_source());
+ if (clone) {
+ this.didActivateWindow = true;
+ Main.overview.activateWindow(clone.metaWindow, event.get_time());
+ }
},
- _onWindowSelected: function (actor, child) {
- child.background_color = WELL_MENU_SELECTED_COLOR;
+ _setHighlightWindow: function (metaWindow) {
+ let children = this._windowContainer.get_children();
+ for (let i = 0; i < children.length; i++) {
+ let child = children[i];
+ let menuMetaWindow = child._window;
+ if (metaWindow != null && menuMetaWindow == metaWindow) {
+ child.background_color = WELL_MENU_SELECTED_COLOR;
+ } else {
+ child.background_color = TRANSPARENT_COLOR;
+ }
+ }
+ this.emit('highlight-window', metaWindow);
+ },
+
+ // Called while menu has a pointer grab
+ _onMenuEnter: function (actor, event) {
+ let clone = this._findWindowCloneForActor(event.get_source());
+ if (clone) {
+ this._setHighlightWindow(clone.metaWindow);
+ }
+ },
- let window = child._window;
- this.emit('highlight-window', window);
+ // Called while menu has a pointer grab
+ _onMenuLeave: function (actor, event) {
+ let clone = this._findWindowCloneForActor(event.get_source());
+ if (clone) {
+ this._setHighlightWindow(null);
+ }
+ },
+
+ _onWindowUnselected: function (actor, child) {
+ this._setHighlightWindow(null);
+ },
+
+ _onWindowSelected: function (actor, child) {
+ this._setHighlightWindow(child._window);
},
_onWindowActivate: function (actor, child) {
- let window = child._window;
- Main.overview.activateWindow(window, Clutter.get_current_event_time());
+ let metaWindow = child._window;
+ this.didActivateWindow = true;
+ Main.overview.activateWindow(metaWindow, Clutter.get_current_event_time());
},
_onPopdown: function () {
if (this._menu == null) {
this._menu = new WellMenu(this);
- this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
- Main.overview.setHighlightWindow(window);
+ this._menu.connect('highlight-window', Lang.bind(this, function (menu, metaWindow) {
+ Main.overview.setHighlightWindow(metaWindow);
}));
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
let id;
+
+ // If we successfully picked a window, don't reset the workspace
+ // state, since that causes visual noise. The workspace gets
+ // recreated each time we enter the overview
+ if (!isPoppedUp && menu.didActivateWindow)
+ return;
if (isPoppedUp)
id = this.appInfo.get_id();
else
id = null;
+
Main.overview.setApplicationWindowSelection(id);
}));
}
struct _ShellMenuPrivate {
gboolean have_grab;
+ gboolean released_on_source;
+ ClutterActor *source_actor;
+
ClutterActor *selected;
};
static guint shell_menu_signals [LAST_SIGNAL] = { 0 };
static gboolean
-shell_menu_contains (ShellMenu *box,
- ClutterActor *actor)
+shell_menu_contains (ClutterContainer *container,
+ ClutterActor *actor)
{
- while (actor != NULL && actor != (ClutterActor*)box)
+ while (actor != NULL && actor != (ClutterActor*)container)
{
actor = clutter_actor_get_parent (actor);
}
{
ShellMenu *box = SHELL_MENU (actor);
- if (!shell_menu_contains (box, event->source))
+ if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source))
return TRUE;
if (event->source == (ClutterActor*)box)
if (event->button != 1)
return FALSE;
+ if (box->priv->source_actor && !box->priv->released_on_source)
+ {
+ if (box->priv->source_actor == event->source ||
+ (CLUTTER_IS_CONTAINER (box->priv->source_actor) &&
+ shell_menu_contains (CLUTTER_CONTAINER (box->priv->source_actor), event->source)))
+ {
+ /* On the next release, we want to pop down the menu regardless */
+ box->priv->released_on_source = TRUE;
+ return TRUE;
+ }
+ }
+
shell_menu_popdown (box);
- if (!shell_menu_contains (box, event->source))
+ if (!shell_menu_contains (CLUTTER_CONTAINER (box), event->source))
return FALSE;
if (box->priv->selected == NULL)
guint32 activate_time)
{
box->priv->have_grab = TRUE;
+ box->priv->released_on_source = FALSE;
clutter_grab_pointer (CLUTTER_ACTOR (box));
}
g_signal_emit (G_OBJECT (box), shell_menu_signals[POPDOWN], 0);
}
+static void
+on_source_destroyed (ClutterActor *actor,
+ ShellMenu *box)
+{
+ box->priv->source_actor = NULL;
+}
+
+/**
+ * shell_menu_set_persistent_source:
+ * @box:
+ * @source: Actor to use as menu origin
+ *
+ * This function changes the menu behavior on button release. Normally
+ * when the mouse is released anywhere, the menu "pops down"; when this
+ * function is called, if the mouse is released over the source actor,
+ * the menu stays.
+ *
+ * The given @source actor must be reactive for this function to work.
+ */
+void
+shell_menu_set_persistent_source (ShellMenu *box,
+ ClutterActor *source)
+{
+ if (box->priv->source_actor)
+ {
+ g_signal_handlers_disconnect_by_func (G_OBJECT (box->priv->source_actor),
+ G_CALLBACK (on_source_destroyed),
+ box);
+ }
+ box->priv->source_actor = source;
+ if (box->priv->source_actor)
+ {
+ g_signal_connect (G_OBJECT (box->priv->source_actor),
+ "destroy",
+ G_CALLBACK (on_source_destroyed),
+ box);
+ }
+}
+
/**
* shell_menu_append_separator:
* @box: